/*
 * Copyright (C) 2010, Funcom AS, All Rights Reserved
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  @authors
 *  Svein Gunnar Larsen <sveingunnarlarsen@gmail.com>
 *  Erik Thune Lund <culex.eriklund@gmail.com>
 *  Michel Krohn-Dale <kronicz@gmail.com>
 */

package com.funcom.peeler;

import com.funcom.peeler.peelable.PeelComponent;
import com.funcom.peeler.peelable.PeelContainer;
import com.funcom.peeler.symbol.Symbol;
import com.jme.renderer.ColorRGBA;
import com.jmex.bui.BComponent;
import com.jmex.bui.BStyleSheet;
import com.jmex.bui.Rule;
import com.jmex.bui.enumeratedConstants.HorizontalAlignment;
import com.jmex.bui.enumeratedConstants.ImageBackgroundMode;
import com.jmex.bui.enumeratedConstants.TextEffect;
import com.jmex.bui.enumeratedConstants.VerticalAlignment;
import com.jmex.bui.property.BackgroundProperty;
import com.jmex.bui.property.CursorProperty;
import com.jmex.bui.property.FontProperty;
import com.jmex.bui.property.IconProperty;
import com.jmex.bui.provider.ResourceProvider;
import com.jmex.bui.util.Dimension;
import com.jmex.bui.util.Insets;
import com.jmex.bui.util.TokReader;

import java.io.Reader;
import java.util.*;

public class PeelStyleSheet extends BStyleSheet{

    public HashMap< String, Rule > properties = new HashMap< String, Rule >();
    public PeelStyleSheet parent;

    public PeelStyleSheet(final Reader reader, final ResourceProvider rsrcprov) {
        super(reader, rsrcprov);
        clone_rules();
    }

    public PeelStyleSheet() {
        super(null, null);
    }

    protected PeelStyleSheet(final ResourceProvider rsrcprov) {
        super( null, rsrcprov);
        clone_rules();
    }

    public synchronized HashMap< String, Rule > getRules() {
        return new HashMap<String, Rule> (_rules);
    }

    private void clone_rules() {

        try {

            for ( Map.Entry< String, Rule > rule_entry : _rules.entrySet() ) {

                // Copy the rule
                Rule rule_copy = new Rule();
                rule_copy.styleClass  = rule_entry.getValue().styleClass;
                rule_copy.pseudoClass = rule_entry.getValue().pseudoClass;

                if ( rule_entry.getValue().properties instanceof HashMap )
                    rule_copy.properties = (HashMap< String, Object >)rule_entry.getValue().properties.clone();

                // Store the exportable Property
                properties.put( rule_entry.getKey(), rule_copy );
            }
        }
        catch ( ClassCastException e )
        {
            System.out.println( e );
        }
    }

    public void setResourceProvider( ResourceProvider rsrcprov ) {
        if ( rsrcprov == null ) return;
        this._rsrcprov = rsrcprov;
    }

    public void clone(PeelStyleSheet styleSheet) {

    }

    private Rule copyRule( Rule ruleToCopy, String copy ) {

        Rule newRule = new Rule();

        newRule.styleClass = ruleToCopy.styleClass + copy;
        newRule.pseudoClass = ruleToCopy.pseudoClass;

        newRule.properties = (HashMap<String, Object>) ruleToCopy.properties.clone();

        return newRule;
    }

    public boolean haveStyle( String style ) {

        Set<String> stylesInRuleSet = getRules().keySet();

        for (Object aStylesInRuleSet : stylesInRuleSet) {
            String aStyle = (String) aStylesInRuleSet;

            if (style.equals(aStyle)) {
                return true;
            }
        }
        return false;
    }

    public boolean makeNewStyleFromPrevious( String newStyle, PeelComponent component ) {

        if (haveStyle( newStyle )) return false;

        String previousStyle = component.getComponent().getStyleClass();

        try {

            HashMap< String, Rule > copy = new HashMap< String, Rule >(properties);

            for ( Map.Entry < String, Rule > copy_entry : copy.entrySet() ) {

                if (copy_entry.getValue().styleClass.equals( previousStyle )) {

                    Rule rule_copy = new Rule();
                    rule_copy.styleClass  = newStyle;
                    rule_copy.pseudoClass = copy_entry.getValue().pseudoClass;

                    String fqClass;

                    if (rule_copy.pseudoClass != null) {
                        fqClass = rule_copy.styleClass + ":" + rule_copy.pseudoClass;
                    } else {
                        fqClass = rule_copy.styleClass;
                    }

                    if ( copy_entry.getValue().properties instanceof HashMap )
                        rule_copy.properties = (HashMap< String, Object >)copy_entry.getValue().properties.clone();

                    properties.put( fqClass, rule_copy );
                    _rules.put( fqClass, copyRule(rule_copy, ""));

                }
            }
        } catch ( ClassCastException e) {
            System.out.println( e );
        }
        return true;
    }

    public void addAllNoneExistingStyles( PeelComponent component, ArrayList< String > addedRules ) {

        BComponent bComponent = component.getComponent();

        if (bComponent.getProperty( bComponent.getStyleClass()) != null) {

            String styleClass = bComponent.getStyleClass();
            String psudoClass = null;
            String fqClass;

            for (int i = 0; i < Symbol.psudoClasses.length; i++) {

                if (Symbol.psudoClasses[i] == null) {
                    fqClass = styleClass;
                } else {
                    fqClass = styleClass + ":" + Symbol.psudoClasses[i];
                    psudoClass = Symbol.psudoClasses[i];
                }

                if (!properties.containsKey( fqClass )) {

                    if (bComponent.getProperty( fqClass) != null)  {

                        this.properties.put( fqClass , copyRule( (Rule)bComponent.getProperty( fqClass ),"") );
                        this._rules.put( fqClass , copyRule( (Rule)bComponent.getProperty( fqClass ), "" ) );

                        addedRules.add( fqClass );
                    }
                } else {

                    if (addedRules.contains( fqClass )) continue;

                    String fqClassCopy;

                    if (psudoClass == null) {
                        fqClassCopy = fqClass +  "_copy";
                        bComponent.setStyleClass( fqClassCopy );
                    } else {
                        fqClassCopy = styleClass + "_copy" + ":" + psudoClass;
                    }

                    this.properties.put( fqClassCopy , copyRule( (Rule)bComponent.getProperty( fqClass ), "_copy" ) );
                    this._rules.put( fqClassCopy , copyRule( (Rule)bComponent.getProperty( fqClass ), "_copy" ) );
                }
            }
        }

        if (component instanceof PeelContainer) {
            int children = ((PeelContainer) component).getComponentCount();
            for (int i = 0; i < children; i++) {
                addAllNoneExistingStyles(((PeelContainer) component).getChild(i), addedRules );
            }
        }
    }

    public boolean noDefaultStyle(BComponent component) {

        String style = component.getStyleClass();

        Collection<String> stylesInRuleSet = this.getRules().keySet();

        for (Object aStylesInRuleSet : stylesInRuleSet) {
            String rule = (String) aStylesInRuleSet;

            if (style.equals(rule)) {
                return false;
            }
        }

        this.updateStyle(style, null, null);
        return true;
    }

    public boolean clearStyle( String style ) {

        if (style.equals( "root" )) return false;

        properties.remove( style );
        _rules.remove( style );

        updateStyle( style, null, null);

        return true;
    }

    public boolean removeStyle( String style, Set<PeelComponent> allComp) {

        if (style.equals( "root" ) ) return false;

        for (PeelComponent aComp : allComp) {
            // A component in the workbench is using this style and can therefore not be deleted.
            if (aComp.getComponent().getStyleClass().equals(style)) {
                return false;
            }
        }

        properties.remove( style );
        _rules.remove( style );

        return true;
    }

    public Object createProperty(String name, ArrayList args) {

        if ( name.equals( Symbol.StyleProperty.color ) ||
                name.equals( Symbol.StyleProperty.effect_color ) ) {
            return createColor( args );
        } else if ( name.equals( Symbol.StyleProperty.background ) ) {
            return createBackgroundProperty( args );
        } else if ( name.equals( Symbol.StyleProperty.icon ) ) {
            return createIconProperty( args );
        } else if ( name.equals( Symbol.StyleProperty.cursor ) ) {
            return createCursorProperty( args );
        } else if ( name.equals( Symbol.StyleProperty.font ) ) {
            return createFontProperty( args );
        } else if ( name.equals( Symbol.StyleProperty.text_align ) ) {
            return createTextAlign( args );
        } else if ( name.equals( Symbol.StyleProperty.vertical_align ) ) {
            return createVerticalAlign( args );
        } else if ( name.equals( Symbol.StyleProperty.text_effect ) ) {
            return createTextEffect( args );
        } else if ( name.equals( Symbol.StyleProperty.effect_size ) ) {
            return createEffectSize( args );
        } else if ( name.equals( Symbol.StyleProperty.line_spacing ) ) {
            return createLineSpacing( args );
        } else if ( name.equals( Symbol.StyleProperty.padding ) ) {
            return createPadding( args );
        } else if ( name.equals( Symbol.StyleProperty.border ) ||
                    name.equals( Symbol.StyleProperty.border_left ) ||
                    name.equals( Symbol.StyleProperty.border_top ) ||
                    name.equals( Symbol.StyleProperty.border_right ) ||
                    name.equals( Symbol.StyleProperty.border_bottom ) ) {
            return createBorder( name, args );
        } else if ( name.equals( Symbol.StyleProperty.size ) ) {
            return createSize( args );
        } else if ( name.equals( Symbol.StyleProperty.parent ) ) {
            return createParent( args );
        } else if ( name.equals( Symbol.StyleProperty.tooltip ) ) {
            return createToolTip( args );
        } else {
            throw new IllegalArgumentException("Unknown property '" + name + "'");
        }
    }

    protected ColorRGBA createColor( ArrayList args ) {
        return parseColor( (String)args.get(0) );
    }

    protected BackgroundProperty createBackgroundProperty( ArrayList args ) {

        BackgroundProperty bprop = new BackgroundProperty();

        bprop.type = (String)args.get(0);
        if (bprop.type.equals("solid")) {
            bprop.color = parseColor( (String)args.get(1) );
        } else if (bprop.type.equals("image")) {
            bprop.ipath = (String)args.get(1);
            if ( args.size() > 2 ) {
                String scaleModeStr = (String)args.get(2);
                bprop.scaleMode = ImageBackgroundMode.fromStylesheetAttributeString(scaleModeStr);
                if (bprop.scaleMode == ImageBackgroundMode.FRAME_XY && args.size() > 3) {
                    bprop.frame = new Insets();
                    bprop.frame.top = parseInt( args.get(3) );
                    bprop.frame.right = (args.size() > 4) ?
                                        parseInt( args.get(4 ) ) : bprop.frame.top;
                    bprop.frame.bottom = ( args.size() > 5 ) ?
                                         parseInt( args.get(5) ) : bprop.frame.top;
                    bprop.frame.left = ( args.size() > 6) ?
                                       parseInt( args.get(6) ) : bprop.frame.right;
                }
            }
        } else if (bprop.type.equals("blank")) {
            // nothing to do

        } else {
            throw new IllegalArgumentException(
                    "Unknown background type: '" + bprop.type + "'");
        }
        return bprop;
    }

    protected IconProperty createIconProperty( ArrayList args ) {

        IconProperty iprop = new IconProperty();
            iprop.type = (String)args.get(0);
            if (iprop.type.equals("image")) {
                iprop.ipath = (String)args.get(1);
            } else if (iprop.type.equals("blank")) {
                iprop.width = parseInt( args.get(1) );
                iprop.height = parseInt( args.get(2) );
            } else {
                throw new IllegalArgumentException("Unknown icon type: '" + iprop.type + "'");
            }
            return iprop;
    }

    protected CursorProperty createCursorProperty( ArrayList args ) {

        CursorProperty cprop = new CursorProperty();
        cprop.name = (String)args.get(0);
        return cprop;
    }

    protected FontProperty createFontProperty( ArrayList args ) {

        try {
            FontProperty fprop = new FontProperty();
            fprop.family = (String)args.get(0);
            fprop.style = (String)args.get(1);
            if (!fprop.style.equals(Symbol.FontStyle.plain.value) && !fprop.style.equals(Symbol.FontStyle.bold.value) &&
                !fprop.style.equals(Symbol.FontStyle.italic.value) && !fprop.style.equals(Symbol.FontStyle.bold_italic.value)) {
                throw new IllegalArgumentException("Unknown font style: '" + fprop.style + "'");
            }
            fprop.size = parseInt( args.get(2) );
            return fprop;
        } catch (Exception e) {
            e.printStackTrace(System.err);
            throw new IllegalArgumentException(
                    "Fonts must be specified as: " +
                    "\"Font name\" plain|bold|italic|bolditalic point-size");
        }
    }

    protected HorizontalAlignment createTextAlign( ArrayList args ) {

        String type = (String)args.get(0);
        return HorizontalAlignment.fromStylesheetAttributeString(type);
    }

    protected VerticalAlignment createVerticalAlign( ArrayList args ) {
        String type = (String)args.get(0);
            return VerticalAlignment.fromStylesheetAttributeString(type);
    }

    protected TextEffect createTextEffect( ArrayList args ) {
        String type = (String)args.get(0);
            return TextEffect.fromStylesheetAttributeString(type);
    }

    protected int createEffectSize( ArrayList args ) {
        return parseInt( args.get(0) );
    }

    protected int createLineSpacing( ArrayList args ) {
        return parseInt( args.get(0) );
    }

    protected Insets createPadding( ArrayList args ) {

        Insets insets = new Insets();
        insets.top = parseInt( args.get(0) );
        insets.right = (args.size() > 1) ? parseInt( args.get(1) ) : insets.top;
        insets.bottom = (args.size() > 2) ? parseInt( args.get(2) ) : insets.top;
        insets.left = (args.size() > 3) ? parseInt( args.get(3) ) : insets.right;
        return insets;
    }

    protected BorderProperty createBorder( String name, ArrayList args ) {

        int thickness = parseInt( args.get(0) );
        String type = (String)args.get(1);
        ColorRGBA color = null;

        if ( args.size() > 2 ) color = parseColor( (String)args.get(2) );

        return new BorderProperty( name, type, thickness, color );
    }

    protected Dimension createSize( ArrayList args ) {

        Dimension size = new Dimension();
        size.width = parseInt( args.get(0) );
        size.height = parseInt( args.get(1) );
        return size;
    }

    protected Rule createParent( ArrayList args ) {

        Rule parent = _rules.get( args.get(0) );
        if (parent == null) {
            throw new IllegalArgumentException("Unknown parent class '" + (String)args.get(0) + "'");
        }
        return parent;
    }

    protected String createToolTip( ArrayList args ) {
        return (String)args.get(0);
    }

    public String getFilePath() {
        return ResourceLoader.getInstance().getStyleSheetPath( this );
    }

    public void updateStyle( String fqStyle, String property, Object value ) {

        if ( !properties.containsKey( fqStyle ) ) {
            properties.put( fqStyle, createRule( fqStyle ) );
            _rules.put( fqStyle, createRule( fqStyle ) );
        }

        if (property != null) {
            properties.get( fqStyle ).properties.put( property, value );
            _rules.get( fqStyle ).properties.put( property, value );
        }
    }

    private Rule createRule( String fqStyle ) {

        Rule rule = new Rule();

        if ( fqStyle.indexOf( ":" ) != -1 ) {

            rule.styleClass  = fqStyle.substring( 0, fqStyle.indexOf( ":" ) );
            rule.pseudoClass = fqStyle.substring( fqStyle.indexOf( ":" ) + 1 );
        }
        else {

            rule.styleClass = fqStyle;
        }

        return rule;
    }

    public Object findProperty(BComponent component,
                              String pseudoClass,
                              String property,
                              boolean climb) {

        Object value = super.findProperty( component, pseudoClass, property, climb );

        if ( value == null && parent != null ) {
            parent.findProperty( component, pseudoClass, property, climb );
        }

        return value;
    }

    public Object findPropertyObject( PeelComponent component,
                                       String pseudoClass,
                                       String property,
                                       boolean climb ) {

        Object value;

                // first try this component's configured style class
        String styleClass = component.getComponent().getStyleClass();
        String fqClass = TokReader.makeFQClass(styleClass, pseudoClass);
        if ((value = getResolvedPropertyObject(fqClass, property)) != null) {
            return value;
        }

        // next fall back to its un-qualified configured style
        if (pseudoClass != null) {
            if ((value = getResolvedPropertyObject(styleClass, property)) != null) {
                return value;
            }
        }

        // if applicable climb up the hierarch to its parent and try there
        if (climb) {
            PeelComponent parent = component.getParent();
            if (parent != null) {
                return findPropertyObject(parent, pseudoClass, property, climb);
            }
        }

        // finally check the "root" class
        fqClass = TokReader.makeFQClass("root", pseudoClass);
        if ((value = getResolvedPropertyObject(fqClass, property)) != null) {
            return value;
        }
        if (pseudoClass != null) {
            return getResolvedPropertyObject("root", property);
        }

        if ( parent != null ) {
            parent.findPropertyObject( component, pseudoClass, property, climb );
        }

        return null;
    }

    protected Object getResolvedPropertyObject(String fqClass,
                                 String property) {
        Rule rule = properties.get(fqClass);
        if (rule == null) {
            return null;
        }

        return rule.get(properties, property);
    }

    protected int parseInt( Object arg ) {
        return (int) ((Double) arg).doubleValue();
    }

    protected ColorRGBA parseColor(String hex) {
        if (!hex.startsWith("#") || (hex.length() != 7 && hex.length() != 9)) {
            String errmsg = "Color must be #RRGGBB or #RRGGBBAA: " + hex;
            throw new IllegalArgumentException(errmsg);
        }
        float r = Integer.parseInt(hex.substring(1, 3), 16) / 255f;
        float g = Integer.parseInt(hex.substring(3, 5), 16) / 255f;
        float b = Integer.parseInt(hex.substring(5, 7), 16) / 255f;
        float a = 1f;
        if (hex.length() == 9) {
            a = Integer.parseInt(hex.substring(7, 9), 16) / 255f;
        }
        return new ColorRGBA(r, g, b, a);
    }
}
